"""
多分类基础

在最后一层线性变换后,接入softmax层
softmax层的目的是保证输出结果一定>=0且是离散的(所有的概率之和为1),采用的方式如下:
1、将线性变换后的输出值,都进行指数运算e^x,此时能保证输出值一定>=0;
2、将指数运算完毕的值进行求和,然后值除以和,计算占的比例,保证此时的输出之和=1;

softmax层后,计算图加入log计算,计算出y_hat

log计算图后,进行loss计算,此处使用NLLLose(Negative Log Likelihood Loss),
NLLLose的计算方式如下:
Loss(y,y_hat)=-y*log(y_hat)
此处的y数组中,只有目标类型的值为1,其他非目标类型的都为0

pytorch提供torch.nn.CrossEntropyLoss(交叉熵)损失函数,直接合并了softmax和NLLLose:
CrossEntropyLoss=softmax+NLLLose
"""
import numpy
import numpy as np
import torch

# 目标Y值,代表着标签类型(取值应为0/1/2/3...),可以使用整形的tensor(例如LongTensor),默认的是float
# y = torch.LongTensor([2, 0, 1])
# y = torch.tensor([2, 0, 1], dtype=torch.Long)
# 现在会自动找到合适的类型
y = torch.tensor([2, 0, 1])

# 假定最后一层linear输出的y_hat如下
y_hat0 = torch.tensor([
    # 索引值恰好和y值符合,所以loss应该较小
    [0.1, 0.2, 0.9],  # 索引2最大
    [1.1, 0.1, 0.2],  # 索引0最大
    [0.2, 2.1, 0.1],  # 索引1最大
])
# 对比数据集,这个应该是错误的,loss会偏高
y_hat1 = torch.tensor([
    [0.8, 0.2, 0.3],
    [0.2, 0.3, 0.5],
    [0.2, 0.2, 0.5],
])

# 定义损失
criterion = torch.nn.CrossEntropyLoss()

# 计算损失
loss0 = criterion(y_hat0, y)
loss1 = criterion(y_hat1, y)
print(loss0, loss1)

"""
自定义算法计算
"""

cusY = numpy.array([
    [0, 0, 1],
    [1, 0, 0],
    [0, 1, 0]
])


def myLoss(y_hat, y_val):
    # softmax计算
    y_pred = np.exp(y_hat) / np.exp(y_hat).sum()
    # NLLLose计算
    # loss = (-y_val * np.log(y_pred)).mean()
    loss = (-y_val * np.log(y_pred)).sum()
    return loss


cusLoss0 = myLoss(y_hat0.numpy(), cusY)
cusLoss1 = myLoss(y_hat1.numpy(), cusY)
print(cusLoss0, cusLoss1)
